// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package httptest

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/textproto"
	"strconv"
	"strings"

	"golang.org/x/net/http/httpguts"
)

// ResponseRecorder是http的一个实现。
// 记录其突变，以备日后检测。
type ResponseRecorder struct {
	// 代码是由WriteHeader设置的HTTP响应代码。
	// 
	// 请注意，如果处理程序从未调用WriteHeader或Write，
	// 这可能最终为0，而不是隐式的
	// http。好的。要获取隐式值，请使用结果
	// 方法。
	Code int

	// HeaderMap包含处理程序显式设置的头。
	// 这是一个内部细节。
	// 
	// 已弃用：HeaderMap的存在是为了历史兼容性
	// 不应使用。要访问处理程序返回的头，请使用响应。Result方法返回的头映射。
	HeaderMap http.Header

	// Body是处理程序的写调用发送到的缓冲区。
	// 如果为零，写入操作将被默默地丢弃。
	Body *bytes.Buffer

	// Flush是指处理程序是否调用Flush。
	Flushed bool

	result      *http.Response // 结果返回值的缓存
	snapHeader  http.Header    // 头映射第一次写入时的快照
	wroteHeader bool
}

// NewRecorder返回初始化的响应记录器。
func NewRecorder() *ResponseRecorder {
	return &ResponseRecorder{
		HeaderMap: make(http.Header),
		Body:      new(bytes.Buffer),
		Code:      200,
	}
}

// DefaultRemoteAddr是在ResponseRecorder上未设置显式DefaultRemoteAddr时，在RemoteAddr中返回的默认远程地址。
const DefaultRemoteAddr = "1.2.3.4"

// 头实现http。回复作者。它返回响应
// 头以在处理程序中进行变异。要测试在处理程序完成后写入的头，请使用Result方法，并查看返回的响应值的头。
func (rw *ResponseRecorder) Header() http.Header {
	m := rw.HeaderMap
	if m == nil {
		m = make(http.Header)
		rw.HeaderMap = m
	}
	return m
}

// 如果尚未写入头，writeHeader会写入头，如果需要，
// 会检测内容类型。
// 
// 字节或str是响应体的开头。
// 我们通过这两个选项来避免在rw中产生不必要的垃圾。出于性能原因而创建的WriteString。
// 非零字节赢。
func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
	if rw.wroteHeader {
		return
	}
	if len(str) > 512 {
		str = str[:512]
	}

	m := rw.Header()

	_, hasType := m["Content-Type"]
	hasTE := m.Get("Transfer-Encoding") != ""
	if !hasType && !hasTE {
		if b == nil {
			b = []byte(str)
		}
		m.Set("Content-Type", http.DetectContentType(b))
	}

	rw.WriteHeader(200)
}

// Write实现http。回复作者。buf中的数据写入
// rw。身体，如果不是零的话。
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
	rw.writeHeader(buf, "")
	if rw.Body != nil {
		rw.Body.Write(buf)
	}
	return len(buf), nil
}

// WriteString实现io。StringWriter。str中的数据被
// 写入rw。身体，如果不是零的话。
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
	rw.writeHeader(nil, str)
	if rw.Body != nil {
		rw.Body.WriteString(str)
	}
	return len(str), nil
}

func checkWriteHeaderCode(code int) {
	// 问题22880:需要有效的写入头状态代码。
	// 目前我们只强制要求它是三位数。
	// 未来我们可能会阻止超过599的内容（600及以上未定义
	// https:
	// 我们可能会阻止低于200的内容（一旦我们有了更成熟的1x支持）。
	// 但现在是任意三位数字。
	// 
	// 我们过去经常在网上发送“HTTP/1.1 000 0”作为回应，但这里有
	// 我们无法在HTTP/2中发送同等的虚假信息，
	// 所以我们会一直惊慌失措，帮助人们尽早发现他们的漏洞。（即使我们愿意，也无法从WriteHeader返回错误。）
	if code < 100 || code > 999 {
		panic(fmt.Sprintf("invalid WriteHeader code %v", code))
	}
}

// WriteHeader实现http。回复作者。
func (rw *ResponseRecorder) WriteHeader(code int) {
	if rw.wroteHeader {
		return
	}

	checkWriteHeaderCode(code)
	rw.Code = code
	rw.wroteHeader = true
	if rw.HeaderMap == nil {
		rw.HeaderMap = make(http.Header)
	}
	rw.snapHeader = rw.HeaderMap.Clone()
}

// Flush实现http。冲洗器。要测试Flush是否被
// 调用，请参阅rw。脸红了。
func (rw *ResponseRecorder) Flush() {
	if !rw.wroteHeader {
		rw.WriteHeader(200)
	}
	rw.Flushed = true
}

// Result返回处理程序生成的响应。
// 
// 返回的响应将至少包含其状态码、
// 标头、正文和可选的尾部。
// 将来可能会填充更多字段，因此呼叫者应该
// 在测试中不等于结果。
// 
// 回复。Header是在
// 第一次写入调用时，或者在该调用时，如果处理程序从未执行过
// 写入操作，那么Header是头的快照。
// 
// 回复。Body保证为非零和Body。Read call是
// 保证不会返回io以外的任何错误。EOF。
// 
// 只能在处理程序完成运行后调用结果。
func (rw *ResponseRecorder) Result() *http.Response {
	if rw.result != nil {
		return rw.result
	}
	if rw.snapHeader == nil {
		rw.snapHeader = rw.HeaderMap.Clone()
	}
	res := &http.Response{
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		StatusCode: rw.Code,
		Header:     rw.snapHeader,
	}
	rw.result = res
	if res.StatusCode == 0 {
		res.StatusCode = 200
	}
	res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode))
	if rw.Body != nil {
		res.Body = io.NopCloser(bytes.NewReader(rw.Body.Bytes()))
	} else {
		res.Body = http.NoBody
	}
	res.ContentLength = parseContentLength(res.Header.Get("Content-Length"))

	if trailers, ok := rw.snapHeader["Trailer"]; ok {
		res.Trailer = make(http.Header, len(trailers))
		for _, k := range trailers {
			k = http.CanonicalHeaderKey(k)
			if !httpguts.ValidTrailerHeader(k) {
				// 因RFC 7230第4.1.2节禁止而忽略。
				continue
			}
			vv, ok := rw.HeaderMap[k]
			if !ok {
				continue
			}
			vv2 := make([]string, len(vv))
			copy(vv2, vv)
			res.Trailer[k] = vv2
		}
	}
	for k, vv := range rw.HeaderMap {
		if !strings.HasPrefix(k, http.TrailerPrefix) {
			continue
		}
		if res.Trailer == nil {
			res.Trailer = make(http.Header)
		}
		for _, v := range vv {
			res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v)
		}
	}
	return res
}

// parseContentLength从s中删除空格，如果未设置值
// 则返回-1，如果大于等于0，则返回值。
// 
// 这是net/http/transfer中相同函数的修改版本。去这个
// 只会忽略一个无效的头。
func parseContentLength(cl string) int64 {
	cl = textproto.TrimString(cl)
	if cl == "" {
		return -1
	}
	n, err := strconv.ParseUint(cl, 10, 63)
	if err != nil {
		return -1
	}
	return int64(n)
}
